<template>
<main class="formDiv noselect colFlex">
  <section class="dotDiv rowFlex">
    <span class="wave rowFlex" ref="wave"></span>
  </section>
  <audio v-for="(item, index) in sources" class="hide" :src="require('@/assets/ryth/media/' + item + '.mp3')" preload="auto"></audio>

  <section class="titleDiv rowFlex">
    <h1 class="titile">节奏猜猜看</h1>
  </section>

  <section class="spaceBox colFlex"></section>

  <section class="midBox colFlex">
    <div class="rowFlex">
      <h2>节奏范围</h2>
      <h2>{{ num[0] + "~" + num[1] + "bpm" }}</h2>
    </div>
    <div class="slider-demo-block rowFlex">
      <h4>{{ range.min }}</h4>
      <el-slider v-model="num" :disabled="ind == 1" :min="range.min" :max="range.max" :step="range.step" size="default" @change="numChanged" range />
      <h4>{{ range.max }}</h4>
    </div>
  </section>

  <section class="footBox colFlex">
    <div class="ansDiv rowFlex">
      <h2>猜节奏</h2>
      <el-input-number v-model="ans" class="mx-4" :disabled="ind == 0" :min="num[0]" :max="num[1]" :step="range.step" size="default" step-strictly controls-position="right" />
    </div>
    <el-button type="primary" @click="ryth">{{
        ind != 1 ? btD[0] : btD[1]
      }}</el-button>
  </section>

  <section class="footBox colFlex">
    <h3 :class="ind == 2 && 'fontR' || ind == 3 && 'fontW'">
      {{ ind == 3 ? instruct[ind] + rythAns : instruct[ind] }}
    </h3>
  </section>

  <section class="footSpace">
  </section>
</main>
</template>

<script>
import {
  Plus
} from "@element-plus/icons-vue";
import {
  ElMessage
} from "element-plus";
export default {
  name: "ryth",
  components: {
    Plus
  },
  data() {
    return {
      workmsgSend: false,
      works: false, //当worker正常运作时为true
      worker: undefined,
      ind: 0,
      num: [80, 120],
      range: {
        min: 10,
        max: 400,
        step: 10
      },
      ans: 80,
      rythAns: 0,
      nowStep: 0,
      lastTime: null,
      btD: ["播放", "确认"],
      instruct: [
        '拖动滑块选取范围，按"播放"',
        '按声音或图像猜节奏，然后"确认"',
        "猜对了!",
        "猜错，节奏是",
      ],
      sources: ["strongBit", "lightBit", "bit"],
    };
  },
  mounted() {
    this.aFrameEdit();
  },
  methods: {
    startWorker() {
      //启用后台线程
      //使用后台线程，避免网页被切换至非活跃时计时器停止运作
      if (Worker != undefined) {
        if (this.worker == undefined) {
          let url = process.env.BASE_URL + "ryth/workers/ryth-worker.js";
          this.worker = new Worker(url);
        }
        this.worker.onmessage = (e) => {
          if (e.data == "sub") {
            this.worker.postMessage("main");
          } else if (e.data == "run") {
            this.running();
            this.works = true;
          }
        };
        setTimeout(() => {
          this.worker.postMessage("next");
        }, 0);
      } else {
        ElMessage.error("浏览器不支持web Workers");
        console.log("浏览器不支持 Web Workers", "error");
      }
      setTimeout(() => {
        if (this.works == false) {
          if (!this.workmsgSend) {
            ElMessage.warning("当前运行环境不支持后台播放");
            this.workmsgSend = true;
          }

          if (this.worker != undefined) {
            this.stopWorker();
          }
          this.running();
        }
      }, 1000);
    },
    stopWorker() {
      //清除后台线程
      this.worker.terminate();
      this.worker = undefined;
    },
    aFrameEdit() {
      //运动框架-编辑定时器
      window.requestAnimationFrame =
        window.requestAnimationFrame ||
        function (fn) {
          return setTimeout(fn, 1000 / 60);
        };
      window.cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
    },
    numChanged() {
      if (this.ans > this.num[1]) {
        this.ans = this.num[1];
      }
      if (this.ans < this.num[0]) {
        this.ans = this.num[0];
      }
    },
    waveMove() {
      let wave = this.$refs.wave;
      if (wave.classList.contains("waveAction")) {
        wave.classList.remove("waveAction");
      } else {
        wave.classList.add("waveAction");
      }
    },
    play(strength) {
      setTimeout(() => {
        let audio = document.querySelectorAll("audio")[strength];

        audio.currentTime = 0;
        audio.play();
      }, 0);
    },
    ryth() {
      let wave = this.$refs.wave;
      if (this.ind != 1) {
        this.ind = 1;

        //随机节奏
        let [min, max, step] = [this.num[0], this.num[1], this.range.step];
        min = min / step;
        max = max / step;
        let bpm = (Math.floor(Math.random() * (max - (min - 1))) + min) * step;
        this.rythAns = bpm;

        //数据初始化
        this.lastTime = new Date();
        this.nowStep = 0;

        this.startWorker();
      } else {
        if (this.ans == this.rythAns) {
          this.ind = 2;
        } else {
          this.ind = 3;
        }

        if (this.worker != undefined) {
          this.stopWorker();
        }

        if (wave.classList.contains("waveAction")) {
          wave.classList.remove("waveAction");
        }
      }
    },
    running() {
      if (this.ind != 1) {
        return false;
      }

      let [lastTime, nowTime, step] = [
        this.lastTime,
        new Date(),
        60 / this.rythAns / 4,
      ];

      let timeStep = (nowTime - lastTime) / 1000;

      let offsetTime = step - timeStep;

      if (timeStep - offsetTime >= step) {
        this.lastTime = nowTime;

        if (this.nowStep == 4 * 4 - 1) {
          this.nowStep = 0;
        } else {
          this.nowStep += 1;
        }

        let int = this.nowStep / 4;
        let dob = int - parseInt(int);

        switch (this.nowStep / 4 - 0.25) {
          case 1:
            this.play(1);
            break;
          case 0:
            this.play(0);
            break;
          case 2:
            this.play(2);
            break;
          case 3:
            this.play(1);
            break;
        }

        if (dob == 0.25 || dob == 0.5) {
          this.waveMove();
        }
      }

      if (this.works == false) {
        requestAnimationFrame(() => {
          this.running();
        });
      } else {
        this.worker.postMessage("next");
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.wave,
.slider-demo-block {
  max-width: 900px;
}

.dotDiv {
  position: absolute;
  width: 100vw;
  height: $vh;

  overflow: hidden;
  z-index: -1;
}

.wave {
  width: 100vw;

  &:before {
    content: ".";
    color: 0;
    font-size: 0;
    box-shadow: 0 0 2.4vw dodgerblue;
    border-radius: 50%;
    background: radial-gradient(transparent 57%, dodgerblue 70%);
  }
}

.waveAction {

  &:before,
  &:after {
    transition: all 0.1s cubic-bezier(0.68, -7, 0.265, 3);
    transform: scale(1.02);
  }
}

.formDiv {
  position: absolute;
  height: $vh;
  top: 0;
  left: 0;

  justify-content: space-between;
}

.slider-demo-block {
  width: 70vw;
  justify-content: space-between;
}

.el-slider {
  margin: 0 0.7rem;
}

.formDiv ::v-deep .el-input-number {
  width: 7rem;
}

.formDiv ::v-deep .el-input__inner {
  width: 5rem;
}

.el-input-number {
  margin-left: 0.3rem;
}

.ansDiv {
  margin-bottom: 1rem;
}

.fontR {
  color: $colorL;
  font-size: 1.2rem;
}

.fontW {
  color: $colorT;
  font-size: 1rem;
}

@media all and (orientation: portrait) {
  $shapSize: 24vw;

  .wave {
    height: $shapSize;

    &:before {
      width: $shapSize;
      height: $shapSize;
    }
  }
}

@media all and (orientation: landscape) {
  $shapSize:calc(#{$vh}*0.24);
  .titleDiv {
    height: calc(#{$vh} * 0.3);
  }

  .spaceBox {
    height: 0;
  }

  .midBox {
    height: calc(#{$vh} * 0.3);
  }

  .footBox {
    height: calc(#{$vh} * 0.3);
  }

  .wave {
    height: $shapSize;
    margin: calc(#{$vh} * 0.31) 0 0;
    max-width: calc(900px);

    &:before {
      position: relative;
      width: $shapSize;
      height: $shapSize;

      box-shadow: 0 0 calc(#{$vh} * 0.024) dodgerblue;
    }

    &:after {
      content: ".";
      position: relative;

      color: 0;
      font-size: 0;
      width: $shapSize;
      height: $shapSize;
      margin-left: 24vw;

      border-radius: 50%;
      box-shadow: 0 0 calc(#{$vh} * 0.024) dodgerblue;
      background: radial-gradient(transparent 57%, dodgerblue 70%);
    }
  }
}

@media all and (orientation: portrait) {
  .titleDiv {
    height: calc(#{$vh} * 0.3);
  }

  .spaceBox {
    height: calc(#{$vh} * 0.2);
  }

  .midBox {
    height: calc(#{$vh} * 0.2);
  }

  .footBox {
    height: calc(#{$vh} * 0.2);
  }

  .footSpace{
    height: calc(#{$vh} * 0.1);
  }

  .wave {
    margin: calc(#{$vh} * -0.44) 0 0;
  }
}
</style>
